﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.ComponentModel;
using System.Threading;
using System.Diagnostics;


using P2PLib.Common.DataModel;
using P2PLib.Common.Command;

namespace P2PLib.Server
{
    public class ClientManager
    {
        private Socket clientSocket;
        private ClientInfo client;
        private NetworkStream stream;
        private BackgroundWorker bgReceiver;
        private DateTime lastResponse;
        private BackgroundWorker bgChecker;

        public event ClientConnectedEventHandler ClientConnected;
        public event ClientDisconnectedEventHandler ClientDisconnected;

        #region Property
        public IPAddress IP
        {
            get
            {
                return ((IPEndPoint)this.clientSocket.RemoteEndPoint).Address;
            }
        }
        public int Port
        {
            get
            {
                return ((IPEndPoint)this.clientSocket.RemoteEndPoint).Port;
            }
        }
        public ClientInfo Client
        {
            get
            {
                return this.client;
            }
        }
        #endregion //Property


        public ClientManager(Socket soc)
        {
            this.clientSocket= soc;
            this.stream = new NetworkStream(soc);
            //初始化lastResponse

            this.lastResponse = DateTime.Now;


            bgReceiver = new BackgroundWorker();
            bgReceiver.DoWork += new DoWorkEventHandler(bgReceiver_DoWork);
            bgReceiver.WorkerSupportsCancellation = true;
            bgReceiver.RunWorkerAsync();


            bgChecker = new BackgroundWorker();
            bgChecker.DoWork += new DoWorkEventHandler(bgChecker_DoWork);
            bgChecker.RunWorkerAsync();
        }
        
        /// <summary>
        /// 向客户端发送checkOnline命令，等待2分钟的时间继续
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void bgChecker_DoWork(object sender, DoWorkEventArgs e)
        {
            while (this.clientSocket.Connected)
            {
                TimeSpan span = DateTime.Now-lastResponse;
                if (span.TotalMilliseconds > 120000)
                {
                    if (this.ClientDisconnected != null)
                    {
                        ClientDisconnected(this, new ClientEventArgs(this.client));
                    }
                    break;
                }
                else
                {
                    CSCheckOnlineCommand command = new CSCheckOnlineCommand();
                    SendCommand(command);
                    Thread.Sleep(30000);
                }
            }
        }

        void bgReceiver_DoWork(object sender, DoWorkEventArgs e)
        {
            Console.WriteLine("Start recieving command from client");

            try
            {
                while (this.clientSocket.Connected)
                {
                    byte[] buffer = new byte[4];
                    int readBytes = stream.Read(buffer, 0, 4);
                    if (readBytes == 0)
                        break;
                    CSCommandType type = (CSCommandType)BitConverter.ToInt32(buffer, 0);

                    Console.WriteLine("Client Manager recived command, type:{0}", type);

                    switch (type)
                    {
                        case CSCommandType.Connect:
                            CSConnectCommand command = new CSConnectCommand();
                            command.ReadFromStream(stream);
                            this.client = command.Client;
                            //调用server端的方法，通知该客户端已经加入到网络
                            if (this.ClientConnected != null)
                                ClientConnected(this, new ClientEventArgs(client));
                            break;

                        case CSCommandType.Disconnect:
                            CSDisconnectCommand command1 = new CSDisconnectCommand();
                            command1.ReadFromStream(stream);
                            //调用server端的方法，通知其他客户端该客户已经离开网络
                            if (this.ClientDisconnected != null)
                                ClientDisconnected(this, new ClientEventArgs(this.client));
                            break;
                        case CSCommandType.CheckResponse:
                            lastResponse = DateTime.Now;
                            break;
                    }
                }
            }
            catch (Exception exception)
            {
                Console.WriteLine("Client:{0} disocnnected...caused by {1}",
                    client.IP.ToString(), exception.StackTrace);
                if (this.ClientDisconnected != null)
                    ClientDisconnected(this, new ClientEventArgs(this.client));
            }
            
        }

        public void SendCommand(CSCommandBase command)
        {
            BackgroundWorker bgSender = new BackgroundWorker();
            bgSender.DoWork += new DoWorkEventHandler(bgSender_DoWork);
            bgSender.RunWorkerAsync(command);
        }

        //This Semaphor is to protect the critical section from concurrent access of sender threads.
        System.Threading.Semaphore semaphor = new System.Threading.Semaphore(1, 1);
        void bgSender_DoWork(object sender, DoWorkEventArgs e)
        {
            
            try
            {
                semaphor.WaitOne();
                CSCommandBase command = (CSCommandBase)e.Argument;
                Debug.WriteLine("Clientmanager sending command to " + this.IP.ToString() + ",type:" + command.Type);

                int type = (int)command.Type;
                byte[] buffer = BitConverter.GetBytes(type);
                stream.Write(buffer, 0, 4);
                command.WriteToStream(stream);
                e.Result = true;
                semaphor.Release();
            }
            catch
            {
                e.Result = false;
            }
        }


    }
}
